/*
SRMM

Copyright 2000-2005 Miranda ICQ/IM project, 
Copyright 2006-2009 Joe Kucera
all portions of this codebase are copyrighted to the people 
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
#include "commonheaders.h"
#pragma hdrstop
#include <ctype.h>
#include <malloc.h>
#include <mbstring.h>

extern HINSTANCE g_hInst;

static int logPixelSY;
#define LOGICON_MSG  0
#define LOGICON_URL  1
#define LOGICON_FILE 2
#define LOGICON_NOTICE 3
static PBYTE pLogIconBmpBits[4];
static int logIconBmpSize[ SIZEOF(pLogIconBmpBits) ];

#define STREAMSTAGE_HEADER  0
#define STREAMSTAGE_EVENTS  1
#define STREAMSTAGE_TAIL    2
#define STREAMSTAGE_STOP    3
struct LogStreamData
{
  int stage;
  HANDLE hContact;
  HANDLE hDbEvent, hDbEventLast;
  char *buffer;
  int bufferOffset, bufferLen;
  int eventsToInsert;
  int isEmpty;
  struct MessageWindowData *dlgDat;
};

static char szSep2[40], szSep2_RTL[50];

static void AppendToBuffer(char **buffer, int *cbBufferEnd, int *cbBufferAlloced, const char *fmt, ...)
{
  va_list va;
  int charsDone;

  va_start(va, fmt);
  for (;;) {
    charsDone = mir_vsnprintf(*buffer + *cbBufferEnd, *cbBufferAlloced - *cbBufferEnd, fmt, va);
    if (charsDone >= 0)
      break;
    *cbBufferAlloced += 1024;
    *buffer = (char *) realloc(*buffer, *cbBufferAlloced);
  }
  va_end(va);
  *cbBufferEnd += charsDone;
}

static int AppendToBufferWithRTF(char **buffer, int *cbBufferEnd, int *cbBufferAlloced, TCHAR* line)
{
  DWORD textCharsCount = 0;
  char *d;
  int lineLen;

  if ( line == NULL )
    return 0;

  lineLen = (int)_tcslen(line) * 9 + 8;
  if (*cbBufferEnd + lineLen > *cbBufferAlloced) {
    cbBufferAlloced[0] += (lineLen + 1024 - lineLen % 1024);
    *buffer = (char *) realloc(*buffer, *cbBufferAlloced);
  }

  d = *buffer + *cbBufferEnd;
  strcpy(d, "{\\uc1 ");
  d += 6;

  for (; *line; line++, textCharsCount++) {
    if (*line == '\r' && line[1] == '\n') {
      CopyMemory(d, "\\par ", 5);
      line++;
      d += 5;
    }
    else if (*line == '\n') {
      CopyMemory(d, "\\par ", 5);
      d += 5;
    }
    else if (*line == '\t') {
      CopyMemory(d, "\\tab ", 5);
      d += 5;
    }
    else if (*line == '\\' || *line == '{' || *line == '}') {
      *d++ = '\\';
      *d++ = (char) *line;
    }
    else if (*line < 128) {
      *d++ = (char) *line;
    }
    else d += sprintf(d, "\\u%d ?", *line);
  }

  strcpy(d, "}");
  d++;

  *cbBufferEnd = (int) (d - *buffer);
  return textCharsCount;
}

#if defined( _UNICODE )
  #define FONT_FORMAT "{\\f%u\\fnil\\fcharset%u %S;}"
#else
  #define FONT_FORMAT "{\\f%u\\fnil\\fcharset%u %s;}"
#endif

static char *CreateRTFHeader(struct MessageWindowData *dat)
{
    char *buffer;
    int bufferAlloced, bufferEnd;
    int i;
    LOGFONT lf;
    COLORREF colour;
    HDC hdc;

    hdc = GetDC(NULL);
    logPixelSY = GetDeviceCaps(hdc, LOGPIXELSY);
    ReleaseDC(NULL, hdc);
    bufferEnd = 0;
    bufferAlloced = 1024;
    buffer = (char *) malloc(bufferAlloced);
    buffer[0] = '\0';
    AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "{\\rtf1\\ansi\\deff0{\\fonttbl");

    for (i = 0; i < msgDlgFontCount; i++) {
        LoadMsgDlgFont(i, &lf, NULL);
        AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, FONT_FORMAT, i, lf.lfCharSet, lf.lfFaceName);
    }
    AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "}{\\colortbl ");
    for (i = 0; i < msgDlgFontCount; i++) {
        LoadMsgDlgFont(i, NULL, &colour);
        AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "\\red%u\\green%u\\blue%u;", GetRValue(colour), GetGValue(colour), GetBValue(colour));
    }
    if (GetSysColorBrush(COLOR_HOTLIGHT) == NULL)
        colour = RGB(0, 0, 255);
    else
        colour = GetSysColor(COLOR_HOTLIGHT);
    AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "\\red%u\\green%u\\blue%u;", GetRValue(colour), GetGValue(colour), GetBValue(colour));
    AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "}");
    //AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "}\\pard");
    return buffer;
}

//free() the return value
static char *CreateRTFTail(struct MessageWindowData *dat)
{
    char *buffer;
    int bufferAlloced, bufferEnd;

    bufferEnd = 0;
    bufferAlloced = 1024;
    buffer = (char *) malloc(bufferAlloced);
    buffer[0] = '\0';
    AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "}");
    return buffer;
}

//return value is static
static char *SetToStyle(int style)
{
    static char szStyle[128];
    LOGFONT lf;

    LoadMsgDlgFont(style, &lf, NULL);
    wsprintfA(szStyle, "\\f%u\\cf%u\\b%d\\i%d\\fs%u", style, style, lf.lfWeight >= FW_BOLD ? 1 : 0, lf.lfItalic, 2 * abs(lf.lfHeight) * 74 / logPixelSY);
    return szStyle;
}

int DbEventIsShownCustomEvent(DBEVENTINFO *dbei)
{
  if (ServiceExists(MS_DB_EVENT_GETTYPE))
  {
    DBEVENTTYPEDESCR* et = ( DBEVENTTYPEDESCR* )CallService( MS_DB_EVENT_GETTYPE, ( WPARAM )dbei->szModule, ( LPARAM )dbei->eventType );
    return et && ( et->flags & DETF_MSGWINDOW );
  }
  return 0;
}

int DbEventIsShown(DBEVENTINFO * dbei, struct MessageWindowData *dat)
{
  switch (dbei->eventType) {
    case EVENTTYPE_MESSAGE:
      return 1;
    case EVENTTYPE_URL:
      if (SRMMGetSettingByte(SRMSGSET_SHOWURLS, SRMSGDEFSET_SHOWURLS))
        return 1;
      break;
    case EVENTTYPE_FILE:
      if (SRMMGetSettingByte(SRMSGSET_SHOWFILES, SRMSGDEFSET_SHOWFILES))
        return 1;
      break;
    default:
      return DbEventIsShownCustomEvent(dbei) && SRMMGetSettingByte(SRMSGSET_SHOWCUSTOMEVENTS, SRMSGDEFSET_SHOWCUSTOMEVENTS);
  }
  return 0;
}

//free() the return value
static char *CreateRTFFromDbEvent(struct MessageWindowData *dat, HANDLE hContact, HANDLE hDbEvent, struct LogStreamData *streamData)
{
  char *buffer;
  int bufferAlloced, bufferEnd;
  DBEVENTINFO dbei = { 0 };
  int showColon = 0;

  dbei.cbSize = sizeof(dbei);
  dbei.cbBlob = CallService(MS_DB_EVENT_GETBLOBSIZE, (WPARAM) hDbEvent, 0);
  if (dbei.cbBlob == -1)
      return NULL;
  dbei.pBlob = (PBYTE) malloc(dbei.cbBlob);
  CallService(MS_DB_EVENT_GET, (WPARAM) hDbEvent, (LPARAM) & dbei);
  if (!DbEventIsShown(&dbei, dat)) {
    free(dbei.pBlob);
    return NULL;
  }
  if (!(dbei.flags & DBEF_SENT) && (dbei.eventType == EVENTTYPE_MESSAGE || dbei.eventType == EVENTTYPE_URL || DbEventIsShownCustomEvent(&dbei))) {
    CallService(MS_DB_EVENT_MARKREAD, (WPARAM) hContact, (LPARAM) hDbEvent);
    CallService(MS_CLIST_REMOVEEVENT, (WPARAM) hContact, (LPARAM) hDbEvent);
  }

  bufferEnd = 0;
  bufferAlloced = 1024;
  buffer = (char *) malloc(bufferAlloced);
  buffer[0] = '\0';

  if (!dat->bIsAutoRTL && !streamData->isEmpty)
    AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "\\par");

  if (dbei.flags & DBEF_RTL) {
    AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "\\rtlpar");
    dat->bIsAutoRTL = TRUE;
  }
  else
    AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "\\ltrpar");

  streamData->isEmpty = 0;

  if (dat->bIsAutoRTL) {
    if(dbei.flags & DBEF_RTL) {
      AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "\\ltrch\\rtlch");
    }else{
      AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "\\rtlch\\ltrch");
    }
  }

  if (g_dat->flags&SMF_SHOWICONS) {
    int i;

    switch (dbei.eventType) {
      case EVENTTYPE_URL:
        i = LOGICON_URL;
        break;
      case EVENTTYPE_FILE:
        i = LOGICON_FILE;
        break;
      case EVENTTYPE_MESSAGE:
        i = LOGICON_MSG;
        break;
      default:
        i = LOGICON_NOTICE;
        break;
    }
    AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "\\f0\\fs14");
    while (bufferAlloced - bufferEnd < logIconBmpSize[i])
      bufferAlloced += 1024;
    buffer = (char *) realloc(buffer, bufferAlloced);
    CopyMemory(buffer + bufferEnd, pLogIconBmpBits[i], logIconBmpSize[i]);
    bufferEnd += logIconBmpSize[i];
  }
  if (g_dat->flags&SMF_SHOWTIME) {
    DBTIMETOSTRINGT dbtts;
    TCHAR str[64];

    if (g_dat->flags&SMF_SHOWSECS)
      dbtts.szFormat = g_dat->flags&SMF_SHOWDATE ? _T("d s") : _T("s");
    else
      dbtts.szFormat = g_dat->flags&SMF_SHOWDATE ? _T("d t") : _T("t");
    dbtts.cbDest = SIZEOF(str);
    dbtts.szDest = str;
    CallService(MS_DB_TIME_TIMESTAMPTOSTRINGT, dbei.timestamp, (LPARAM)&dbtts);
    AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, " %s ", SetToStyle(dbei.flags & DBEF_SENT ? MSGFONTID_MYTIME : MSGFONTID_YOURTIME));
    AppendToBufferWithRTF(&buffer, &bufferEnd, &bufferAlloced, str);
    showColon = 1;
  }
  if (!(g_dat->flags&SMF_HIDENAMES)) {
    TCHAR* szName;
    CONTACTINFO ci = {0};

    if (dbei.flags & DBEF_SENT) {
      ci.cbSize = sizeof(ci);
      ci.hContact = NULL;
      ci.szProto = dbei.szModule;
      ci.dwFlag = CNF_DISPLAY;
#if defined( _UNICODE )
      ci.dwFlag += CNF_UNICODE;
#endif
      if (!CallService(MS_CONTACT_GETCONTACTINFO, 0, (LPARAM) & ci)) {
        // CNF_DISPLAY always returns a string type
        szName = ( TCHAR* )ci.pszVal;
      }
    }
    else szName = ( TCHAR* ) CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM) hContact, GCDNF_TCHAR);

    AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, " %s ", SetToStyle(dbei.flags & DBEF_SENT ? MSGFONTID_MYNAME : MSGFONTID_YOURNAME));
    AppendToBufferWithRTF(&buffer, &bufferEnd, &bufferAlloced, szName);
    showColon = 1;
    if (ci.pszVal)
      mir_free(ci.pszVal);
  }

  if (showColon) 
    AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "%s :", SetToStyle(dbei.flags & DBEF_SENT ? MSGFONTID_MYCOLON : MSGFONTID_YOURCOLON));

  switch (dbei.eventType) {
    case EVENTTYPE_MESSAGE:
    {
      TCHAR* msg;
      BOOL   bNeedsFree = FALSE;

      AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, " %s ", SetToStyle(dbei.flags & DBEF_SENT ? MSGFONTID_MYMSG : MSGFONTID_YOURMSG));

#if defined( _UNICODE )
      {
        int msglen = strlen((char *) dbei.pBlob) + 1;
        if (dbei.flags & DBEF_UTF) {
          mir_utf8decode(dbei.pBlob, &msg);
          bNeedsFree = TRUE;
        }
        else if (msglen*(sizeof(TCHAR)+1) == (int) dbei.cbBlob)
          msg = (TCHAR *) & dbei.pBlob[msglen];
        else {
          msg = (TCHAR *)_alloca(sizeof(TCHAR) * msglen);
          MultiByteToWideChar(CP_ACP, 0, (char *) dbei.pBlob, -1, msg, msglen);
        }
      }
#else
      if (dbei.flags & DBEF_UTF)
        msg = mir_utf8decode(dbei.pBlob, NULL);
      else
        msg = (BYTE *) dbei.pBlob;
#endif
      AppendToBufferWithRTF(&buffer, &bufferEnd, &bufferAlloced, msg);
      if (bNeedsFree)
        mir_free(msg);
      break;
    }
    case EVENTTYPE_URL:
    {
      char* descr = dbei.pBlob + lstrlenA( dbei.pBlob ) + 1;
      AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, " %s ", SetToStyle(dbei.flags & DBEF_SENT ? MSGFONTID_MYURL : MSGFONTID_YOURURL));
      AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "%s", dbei.pBlob);
      if (lstrlenA(descr))
        AppendToBuffer( &buffer, &bufferEnd, &bufferAlloced, " (%s)", descr );
      break;
    }
    case EVENTTYPE_FILE:
    {
      char* buf_filename = dbei.pBlob + sizeof(DWORD);
      char* buf_descr = buf_filename + lstrlenA( buf_filename ) + 1;
      BOOL bNeedsFree = FALSE;
      TCHAR* ptszFileName;
#if defined( _UNICODE )
      {
        if (dbei.flags & DBEF_UTF) {
          mir_utf8decode(buf_filename, &ptszFileName);
          bNeedsFree = TRUE;
        }
        else
        {
          ptszFileName = (TCHAR *)_alloca(sizeof(TCHAR) * (lstrlenA(buf_filename) + 1));
          MultiByteToWideChar(CP_ACP, 0, (char *) buf_filename, -1, ptszFileName, lstrlenA(buf_filename) + 1);
      } }
#else
      if (dbei.flags & DBEF_UTF)
        ptszFileName = mir_utf8decode(buf_filename, NULL);
      else
        ptszFileName = buf_filename;
#endif

      AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, " %s ", SetToStyle(dbei.flags & DBEF_SENT ? MSGFONTID_MYFILE : MSGFONTID_YOURFILE));
      AppendToBufferWithRTF(&buffer, &bufferEnd, &bufferAlloced,
        (dbei.flags & DBEF_SENT) ? TranslateT("File sent") : TranslateT("File received"));
      AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, ": ");
      AppendToBufferWithRTF(&buffer, &bufferEnd, &bufferAlloced, ptszFileName);
      if (bNeedsFree)
        mir_free(ptszFileName);
      if ( *buf_descr != 0 ) {
        TCHAR* ptszDescr;

        bNeedsFree = FALSE;
#if defined( _UNICODE )
        {
          if (dbei.flags & DBEF_UTF) {
            mir_utf8decode(buf_descr, &ptszDescr);
            bNeedsFree = TRUE;
          }
          else
          {
            ptszDescr = (TCHAR *)_alloca(sizeof(TCHAR) * (lstrlenA(buf_descr) + 1));
            MultiByteToWideChar(CP_ACP, 0, (char *) buf_descr, -1, ptszDescr, lstrlenA(buf_descr) + 1);
        } }
#else
        if (dbei.flags & DBEF_UTF)
          ptszDescr = mir_utf8decode(buf_descr, NULL);
        else
          ptszDescr = buf_descr;
#endif
        AppendToBuffer( &buffer, &bufferEnd, &bufferAlloced, " (" );
        AppendToBufferWithRTF(&buffer, &bufferEnd, &bufferAlloced, ptszDescr);
        AppendToBuffer( &buffer, &bufferEnd, &bufferAlloced, ")" );
        if (bNeedsFree)
          mir_free(ptszDescr);
      }
      break;
    }
    default:
      if (DbEventIsShownCustomEvent(&dbei))
      { // get custom event text
        TCHAR* msg = DbGetEventTextT( &dbei, CP_ACP );
        AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, " %s ", SetToStyle(MSGFONTID_NOTIFICATION));
        if ( msg ) {
          AppendToBufferWithRTF(&buffer, &bufferEnd, &bufferAlloced, msg);
          mir_free( msg );
        }
        else
          AppendToBufferWithRTF(&buffer, &bufferEnd, &bufferAlloced, TranslateT("Unknown notification"));
      }
  }
  if(dat->bIsAutoRTL)
    AppendToBuffer(&buffer, &bufferEnd, &bufferAlloced, "\\par");

  free(dbei.pBlob);
  return buffer;
}

static DWORD CALLBACK LogStreamInEvents(DWORD_PTR dwCookie, LPBYTE pbBuff, LONG cb, LONG * pcb)
{
    struct LogStreamData *dat = (struct LogStreamData *) dwCookie;

    if (dat->buffer == NULL) {
        dat->bufferOffset = 0;
        switch (dat->stage) {
            case STREAMSTAGE_HEADER:
                dat->buffer = CreateRTFHeader(dat->dlgDat);
                dat->stage = STREAMSTAGE_EVENTS;
                break;
            case STREAMSTAGE_EVENTS:
                if (dat->eventsToInsert) {
                    do {
                        dat->buffer = CreateRTFFromDbEvent(dat->dlgDat, dat->hContact, dat->hDbEvent, dat);
                        if (dat->buffer)
                            dat->hDbEventLast = dat->hDbEvent;
                        dat->hDbEvent = (HANDLE) CallService(MS_DB_EVENT_FINDNEXT, (WPARAM) dat->hDbEvent, 0);
                        if (--dat->eventsToInsert == 0)
                            break;
                    } while (dat->buffer == NULL && dat->hDbEvent);
                    if (dat->buffer) {
                        dat->isEmpty = 0;
                        break;
                    }
                }
                dat->stage = STREAMSTAGE_TAIL;
                //fall through
            case STREAMSTAGE_TAIL:
                dat->buffer = CreateRTFTail(dat->dlgDat);
                dat->stage = STREAMSTAGE_STOP;
                break;
            case STREAMSTAGE_STOP:
                *pcb = 0;
                return 0;
        }
        dat->bufferLen = lstrlenA(dat->buffer);
    }
    *pcb = min(cb, dat->bufferLen - dat->bufferOffset);
    CopyMemory(pbBuff, dat->buffer + dat->bufferOffset, *pcb);
    dat->bufferOffset += *pcb;
    if (dat->bufferOffset == dat->bufferLen) {
        free(dat->buffer);
        dat->buffer = NULL;
    }
    return 0;
}

void StreamInEvents(HWND hwndDlg, HANDLE hDbEventFirst, int count, int fAppend)
{
    EDITSTREAM stream = { 0 };
    struct LogStreamData streamData = { 0 };
    struct MessageWindowData *dat = (struct MessageWindowData *) GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
    CHARRANGE oldSel, sel;

    SendDlgItemMessage(hwndDlg, IDC_LOG, EM_HIDESELECTION, TRUE, 0);
    SendDlgItemMessage(hwndDlg, IDC_LOG, EM_EXGETSEL, 0, (LPARAM) & oldSel);
    streamData.hContact = dat->hContact;
    streamData.hDbEvent = hDbEventFirst;
    streamData.dlgDat = dat;
    streamData.eventsToInsert = count;
    streamData.isEmpty = fAppend ? GetWindowTextLength(GetDlgItem(hwndDlg, IDC_LOG)) == 0 : 1;
    stream.pfnCallback = LogStreamInEvents;
    stream.dwCookie = (DWORD_PTR) & streamData;
    if (fAppend) {
        sel.cpMin = sel.cpMax = GetWindowTextLength(GetDlgItem(hwndDlg, IDC_LOG));
        SendDlgItemMessage(hwndDlg, IDC_LOG, EM_EXSETSEL, 0, (LPARAM) & sel);
    }
    else dat->bIsFirstAppend = TRUE;

    strcpy(szSep2, fAppend ? "\\par\\sl0" : "\\sl1000");
    strcpy(szSep2_RTL, fAppend ? "\\rtlpar\\rtlmark\\par\\sl1000" : "\\sl1000");

    SendDlgItemMessage(hwndDlg, IDC_LOG, EM_STREAMIN, fAppend ? SFF_SELECTION | SF_RTF : SF_RTF, (LPARAM) & stream);
    SendDlgItemMessage(hwndDlg, IDC_LOG, EM_EXSETSEL, 0, (LPARAM) & oldSel);
    SendDlgItemMessage(hwndDlg, IDC_LOG, EM_HIDESELECTION, FALSE, 0);
    dat->hDbEventLast = streamData.hDbEventLast;
    if (GetWindowLongPtr(GetDlgItem(hwndDlg, IDC_LOG), GWL_STYLE) & WS_VSCROLL)
        PostMessage(hwndDlg, DM_SCROLLLOGTOBOTTOM, 0, 0);
}

#define RTFPICTHEADERMAXSIZE   78
void LoadMsgLogIcons(void)
{
    HICON hIcon;
    HBITMAP hBmp, hoBmp;
    HDC hdc, hdcMem;
    BITMAPINFOHEADER bih = { 0 };
    int widthBytes, i;
    RECT rc;
    HBRUSH hBkgBrush;
    int rtfHeaderSize;
    PBYTE pBmpBits;

    hBkgBrush = CreateSolidBrush(SRMMGetSettingDword(SRMSGSET_BKGCOLOUR, SRMSGDEFSET_BKGCOLOUR));
    bih.biSize = sizeof(bih);
    bih.biBitCount = 24;
    bih.biCompression = BI_RGB;
    bih.biHeight = GetSystemMetrics(SM_CYSMICON);
    bih.biPlanes = 1;
    bih.biWidth = GetSystemMetrics(SM_CXSMICON);
    widthBytes = ((bih.biWidth * bih.biBitCount + 31) >> 5) * 4;
    rc.top = rc.left = 0;
    rc.right = bih.biWidth;
    rc.bottom = bih.biHeight;
    hdc = GetDC(NULL);
    hBmp = CreateCompatibleBitmap(hdc, bih.biWidth, bih.biHeight);
    hdcMem = CreateCompatibleDC(hdc);
    pBmpBits = (PBYTE) malloc(widthBytes * bih.biHeight);
    for (i = 0; i < SIZEOF(pLogIconBmpBits); i++) {
        switch (i) {
            case LOGICON_URL:
                hIcon = LoadSkinnedIcon(SKINICON_EVENT_URL);
                break;
            case LOGICON_FILE:
                hIcon = LoadSkinnedIcon(SKINICON_EVENT_FILE);
                break;
            case LOGICON_MSG:
                hIcon = LoadSkinnedIcon(SKINICON_EVENT_MESSAGE);
                break;
            default:
                // BEWARE!! HACK, changing icon size
                bih.biHeight = bih.biWidth = 10;
                widthBytes = ((bih.biWidth * bih.biBitCount + 31) >> 5) * 4;
                rc.right = bih.biWidth;
                rc.bottom = bih.biHeight;
                DeleteObject(hBmp);
                hBmp = CreateCompatibleBitmap(hdc, bih.biWidth, bih.biHeight);

                hIcon = LoadImage(g_hInst, MAKEINTRESOURCE(IsWinVerXPPlus()? IDI_NOTICE32 : IDI_NOTICE), IMAGE_ICON, 10, 10, LR_DEFAULTCOLOR);
                break;
        }
        pLogIconBmpBits[i] = (PBYTE) malloc(RTFPICTHEADERMAXSIZE + (bih.biSize + widthBytes * bih.biHeight) * 2);
        //I can't seem to get binary mode working. No matter.
        rtfHeaderSize = sprintf(pLogIconBmpBits[i], "{\\pict\\dibitmap0\\wbmbitspixel%u\\wbmplanes1\\wbmwidthbytes%u\\picw%u\\pich%u ", bih.biBitCount, widthBytes, bih.biWidth, bih.biHeight);
        hoBmp = (HBITMAP) SelectObject(hdcMem, hBmp);
        FillRect(hdcMem, &rc, hBkgBrush);
        DrawIconEx(hdcMem, 0, 0, hIcon, bih.biWidth, bih.biHeight, 0, NULL, DI_NORMAL);
        SelectObject(hdcMem, hoBmp);
        GetDIBits(hdc, hBmp, 0, bih.biHeight, pBmpBits, (BITMAPINFO *) & bih, DIB_RGB_COLORS);
        {
            int n;
            for (n = 0; n < sizeof(BITMAPINFOHEADER); n++)
                sprintf(pLogIconBmpBits[i] + rtfHeaderSize + n * 2, "%02X", ((PBYTE) & bih)[n]);
            for (n = 0; n < widthBytes * bih.biHeight; n += 4)
                sprintf(pLogIconBmpBits[i] + rtfHeaderSize + (bih.biSize + n) * 2, "%02X%02X%02X%02X", pBmpBits[n], pBmpBits[n + 1], pBmpBits[n + 2], pBmpBits[n + 3]);
        }
        logIconBmpSize[i] = rtfHeaderSize + (bih.biSize + widthBytes * bih.biHeight) * 2 + 1;
        pLogIconBmpBits[i][logIconBmpSize[i] - 1] = '}';

        if (i == LOGICON_NOTICE)
          DestroyIcon(hIcon);
    }
    SAFE_FREE(&pBmpBits);
    DeleteDC(hdcMem);
    DeleteObject(hBmp);
    ReleaseDC(NULL, hdc);
    DeleteObject(hBkgBrush);
}

void FreeMsgLogIcons(void)
{
  int i;

  for (i = 0; i < SIZEOF(pLogIconBmpBits); i++)
    SAFE_FREE(&pLogIconBmpBits[i]);
}
